[1] "this is a code block"
Preliminary Network Visualization with R
Learning Outcomes
- Understanding Quarto and RMarkdown documents
- Understanding functions and arguments
- Loading a data set:
read_csv() - Peeking at a data set:
head(),tail(),glimpse() - Wrangling data frames with {tidyverse} commands:
filter(),rename() - Creating a data frame from scratch with field names:
data.frame() - Running functions from {visNetwork} and {igraph} to create networks and understanding argument values:
visNetwork(),graph_from_data_frame(),E(),V() - Writing out {igraph} objects to a file, e.g., in “.csv” or “.graphml” format
- Calculating node-level centrality metrics to describe network structure
- Calculating clustering coefficients
Introduction
Today, we are going to do some preliminary exercises for working with network data using R and RStudio. You should use a “Quarto” or “RMarkdown” document for organizing your work and notes to get practice using that approach to doing “reproducible research”. Both of these are simple text documents that allow you to mix together narrative text and code to generate formatted, dynamic output. It also allows you to easily manage notes and run code.
Quarto/RMarkdown and R Basics
Use the File > New File > Quarto Document… or File > New File > R Markdown… command to create new Quarto or R Markdown documents.
Use hashtags (#) to format headers for section titles. The more #’s, the smaller the header.
Use markdown syntax to format text
- Wrap text in asterisks (e.g., *italics*) to create italicized text
- Wrap text in double asterisks (e.g., **bold**) to create bold text
- Wrap text in carats (e.g., ^superscript^) to create superscript text
- Wrap text in tildes (e.g., ~subscript~^) to create subscript text
- Wrap text in double tildes (e.g., ~~strikethrough~~) to create
strikethroughtext - Wrap text in back ticks (e.g., `inline code`) to create
inline code
Chunks of R code are included in code blocks that start and end with three back ticks. The opening line of the code block should also include the designator {r}, which tells the rendering engine to run the code using R (as opposed to Python, etc.)
```{r}
# example code block syntax
print("this is a code block")
```Run code by highlighting it in your document and hitting
command-RETURNorcommand-ENTER. This sends the code to the R console and executes it.A whole block of code can be run by clicking the green right arrow at the top of the code block
Comments can be included within R code blocks by prefacing them with #
Use the Render (for Quarto documents) or Knit (for RMarkdown documents) to create nicely formatted reports based on your notes and code. These commands read through your text file and create an HTML or PDF file that incorporates your syntax, runs the code in your code blocks, and inserts the output of running that code into the report.
Today’s Exercise
Load in a dataset
We will use a slightly different version of the dataset we were working with last time: social connections among characters in the Game of Thrones saga, but only from book one.
# read in edges and vertices as data frames/tibbles from .csv files using the {tidyverse} package
library(tidyverse)f <- file.choose()e <- read_csv(f, col_names = TRUE)
head(e)# A tibble: 6 × 5
Source Target Type weight book
<chr> <chr> <chr> <dbl> <dbl>
1 Addam-Marbrand Jaime-Lannister Undirected 3 1
2 Addam-Marbrand Tywin-Lannister Undirected 6 1
3 Aegon-I-Targaryen Daenerys-Targaryen Undirected 5 1
4 Aegon-I-Targaryen Eddard-Stark Undirected 4 1
5 Aemon-Targaryen-(Maester-Aemon) Alliser-Thorne Undirected 4 1
6 Aemon-Targaryen-(Maester-Aemon) Bowen-Marsh Undirected 4 1
tail(e)# A tibble: 6 × 5
Source Target Type weight book
<chr> <chr> <chr> <dbl> <dbl>
1 Tyrion-Lannister Varys Undirected 3 1
2 Tyrion-Lannister Willis-Wode Undirected 4 1
3 Tyrion-Lannister Yoren Undirected 10 1
4 Tywin-Lannister Varys Undirected 4 1
5 Tywin-Lannister Walder-Frey Undirected 8 1
6 Waymar-Royce Will-(prologue) Undirected 18 1
glimpse(e)Rows: 684
Columns: 5
$ Source <chr> "Addam-Marbrand", "Addam-Marbrand", "Aegon-I-Targaryen", "Aegon…
$ Target <chr> "Jaime-Lannister", "Tywin-Lannister", "Daenerys-Targaryen", "Ed…
$ Type <chr> "Undirected", "Undirected", "Undirected", "Undirected", "Undire…
$ weight <dbl> 3, 6, 5, 4, 4, 4, 9, 5, 13, 34, 5, 4, 10, 3, 5, 3, 12, 11, 6, 4…
$ book <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
Create a graph using the {visNetwork} package…
We will first use the visNetwork package to do a quick visualization of the network. Start by loading in the package…
library(visNetwork){visNetwork} requires separate data frames for nodes and edges, so we create those from our dataset…
# we first create a data frame with edges and weights as a simple copy of the dataset we have loaded in
edges <- eBecause this dataset is so large, we’re going to limit it to only those edges with a high weight…
# pick out only edges with high weight
edges <- filter(edges, weight >= 50)From this, we then create a data frame of vertices by selecting all the unique names in either the “Source” or “Target” columns…
vertices <- data.frame(
id = unique(c(edges$Source,edges$Target)),
label = unique(c(edges$Source, edges$Target))
# the label argument allows us to display node names
)We then change some column names to meet some requirements for the visNetwork() function, e.g., we change “Source” and “Target” to “from” and “to” and we make the “Type” column start with a lowercase letter…
edges <- rename(edges, from = Source, to = Target, type = Type)We also add new columns for “width”, “label”, and “title” to improve our visualization…
edges <- mutate(edges,
width = weight / max(weight) * 20, # scale the width
label = weight, # label the edges with their weights
title = paste("Weight:", weight) # tooltip for the edges
)Now, we use the visNetwork() function to create and visualize the network… Note that this creates an interactive graph where we can select and move vertices around!
# create the visNetwork graph
visNetwork(nodes = vertices, edges = edges) %>%
visEdges(smooth = TRUE) %>%
visNodes(size = 20) %>%
# set a seed for reproducible layout
visLayout(randomSeed = 42)Questions
Looking at this graph, answer the following questions…
- How many components are in the graph?
- What is the diameter of the largest component?
- Which node(s) has the highest degree? What is that degree?
- If we initially filter our dataset by a higher edge weight (e.g., 60), how do these answers change? What about using a lower edge weight (e.g., 40)?
Create the same graph using the {igraph} package…
# Load the igraph package
library(igraph)
# use the `graph_from_data_frame()` function to create a graph from our data frames of edges and vertices
graph <- graph_from_data_frame(
d = edges,
vertices = vertices,
directed = FALSE)
graphIGRAPH 3d62e27 UNW- 20 18 --
+ attr: name (v/c), label (v/c), type (e/c), weight (e/n), book (e/n),
| width (e/n), label (e/n), title (e/c)
+ edges from 3d62e27 (vertex names):
[1] Arya-Stark --Sansa-Stark Bran-Stark --Jon-Snow
[3] Bran-Stark --Luwin Bran-Stark --Robb-Stark
[5] Bronn --Tyrion-Lannister Catelyn-Stark --Eddard-Stark
[7] Cersei-Lannister --Eddard-Stark Cersei-Lannister --Robert-Baratheon
[9] Daenerys-Targaryen--Drogo Daenerys-Targaryen--Jorah-Mormont
[11] Eddard-Stark --Petyr-Baelish Eddard-Stark --Robert-Baratheon
[13] Eddard-Stark --Varys Jeor-Mormont --Jon-Snow
+ ... omitted several edges
We can use the {igraph} functions E() and V() to get information about the edges and vertices of a graph…
# show edge information
E(graph)+ 18/18 edges from 3d62e27 (vertex names):
[1] Arya-Stark --Sansa-Stark Bran-Stark --Jon-Snow
[3] Bran-Stark --Luwin Bran-Stark --Robb-Stark
[5] Bronn --Tyrion-Lannister Catelyn-Stark --Eddard-Stark
[7] Cersei-Lannister --Eddard-Stark Cersei-Lannister --Robert-Baratheon
[9] Daenerys-Targaryen--Drogo Daenerys-Targaryen--Jorah-Mormont
[11] Eddard-Stark --Petyr-Baelish Eddard-Stark --Robert-Baratheon
[13] Eddard-Stark --Varys Jeor-Mormont --Jon-Snow
[15] Joffrey-Baratheon --Sansa-Stark Jon-Snow --Robb-Stark
[17] Jon-Snow --Samwell-Tarly Jon-Snow --Tyrion-Lannister
E(graph)$type [1] "Undirected" "Undirected" "Undirected" "Undirected" "Undirected"
[6] "Undirected" "Undirected" "Undirected" "Undirected" "Undirected"
[11] "Undirected" "Undirected" "Undirected" "Undirected" "Undirected"
[16] "Undirected" "Undirected" "Undirected"
E(graph)$weight [1] 104 56 65 112 61 64 69 72 101 75 81 291 61 81 87 53 81 56
E(graph)$width [1] 7.147766 3.848797 4.467354 7.697595 4.192440 4.398625 4.742268
[8] 4.948454 6.941581 5.154639 5.567010 20.000000 4.192440 5.567010
[15] 5.979381 3.642612 5.567010 3.848797
# show vertex information
V(graph)+ 20/20 vertices, named, from 3d62e27:
[1] Arya-Stark Bran-Stark Bronn Catelyn-Stark
[5] Cersei-Lannister Daenerys-Targaryen Eddard-Stark Jeor-Mormont
[9] Joffrey-Baratheon Jon-Snow Sansa-Stark Luwin
[13] Robb-Stark Tyrion-Lannister Robert-Baratheon Drogo
[17] Jorah-Mormont Petyr-Baelish Varys Samwell-Tarly
V(graph)$label [1] "Arya-Stark" "Bran-Stark" "Bronn"
[4] "Catelyn-Stark" "Cersei-Lannister" "Daenerys-Targaryen"
[7] "Eddard-Stark" "Jeor-Mormont" "Joffrey-Baratheon"
[10] "Jon-Snow" "Sansa-Stark" "Luwin"
[13] "Robb-Stark" "Tyrion-Lannister" "Robert-Baratheon"
[16] "Drogo" "Jorah-Mormont" "Petyr-Baelish"
[19] "Varys" "Samwell-Tarly"
Use the plot.igraph() function to visualize a graph…
l <- layout_in_circle(graph)
plot.igraph(graph,
vertex.shape="square",
vertex.size = 5,
layout = l)# circle layouts are common, but not always visually informative!{igraph} includes the option to use various “force-directed” layout algorithms for constructing nicer-looking graphs, where the edges are similar in length and cross each other as little as possible
They work by simulating the graph as a physical system
l <- layout_with_kk(graph)
plot.igraph(graph,
vertex.shape="square",
vertex.size = 5,
edge.label = "",
layout = l)# here, edge widths are determined by the "width" variable we defined above, and edge labels are set to be blank
plot.igraph(graph,
vertex.shape="circle",
vertex.size = 8,
edge.width = 4,
layout = l)# here, we set a single edge widths, and edge labels come from the "weight" variableSome other useful {igraph} functions
Other functions from {igraph} can be run to show alternative representations of the graph properties or to return graph properties…
# show adjacency matrix representation
m <- as_adjacency_matrix(graph)
head(m)6 x 20 sparse Matrix of class "dgCMatrix"
Arya-Stark . . . . . . . . . . 1 . . . . . . . . .
Bran-Stark . . . . . . . . . 1 . 1 1 . . . . . . .
Bronn . . . . . . . . . . . . . 1 . . . . . .
Catelyn-Stark . . . . . . 1 . . . . . . . . . . . . .
Cersei-Lannister . . . . . . 1 . . . . . . . 1 . . . . .
Daenerys-Targaryen . . . . . . . . . . . . . . . 1 1 . . .
# show adjacency matrix with weights
m <- as_adjacency_matrix(graph, attr = "weight")
head(m)6 x 20 sparse Matrix of class "dgCMatrix"
Arya-Stark . . . . . . . . . . 104 . . . . . . . . .
Bran-Stark . . . . . . . . . 56 . 65 112 . . . . . . .
Bronn . . . . . . . . . . . . . 61 . . . . . .
Catelyn-Stark . . . . . . 64 . . . . . . . . . . . . .
Cersei-Lannister . . . . . . 69 . . . . . . . 72 . . . . .
Daenerys-Targaryen . . . . . . . . . . . . . . . 101 75 . . .
# show edge list
l <- as_edgelist(graph, names = TRUE)
head(l) [,1] [,2]
[1,] "Arya-Stark" "Sansa-Stark"
[2,] "Bran-Stark" "Jon-Snow"
[3,] "Bran-Stark" "Luwin"
[4,] "Bran-Stark" "Robb-Stark"
[5,] "Bronn" "Tyrion-Lannister"
[6,] "Catelyn-Stark" "Eddard-Stark"
# list vertices adjacent to each vertex
l <- as_adj_list(graph)
head(l)$`Arya-Stark`
+ 1/20 vertex, named, from 3d62e27:
[1] Sansa-Stark
$`Bran-Stark`
+ 3/20 vertices, named, from 3d62e27:
[1] Jon-Snow Luwin Robb-Stark
$Bronn
+ 1/20 vertex, named, from 3d62e27:
[1] Tyrion-Lannister
$`Catelyn-Stark`
+ 1/20 vertex, named, from 3d62e27:
[1] Eddard-Stark
$`Cersei-Lannister`
+ 2/20 vertices, named, from 3d62e27:
[1] Eddard-Stark Robert-Baratheon
$`Daenerys-Targaryen`
+ 2/20 vertices, named, from 3d62e27:
[1] Drogo Jorah-Mormont
# list edges connected to each vertex
l <- as_adj_edge_list(graph)
head(l)$`Arya-Stark`
+ 1/18 edge from 3d62e27 (vertex names):
[1] Arya-Stark--Sansa-Stark
$`Bran-Stark`
+ 3/18 edges from 3d62e27 (vertex names):
[1] Bran-Stark--Jon-Snow Bran-Stark--Luwin Bran-Stark--Robb-Stark
$Bronn
+ 1/18 edge from 3d62e27 (vertex names):
[1] Bronn--Tyrion-Lannister
$`Catelyn-Stark`
+ 1/18 edge from 3d62e27 (vertex names):
[1] Catelyn-Stark--Eddard-Stark
$`Cersei-Lannister`
+ 2/18 edges from 3d62e27 (vertex names):
[1] Cersei-Lannister--Eddard-Stark Cersei-Lannister--Robert-Baratheon
$`Daenerys-Targaryen`
+ 2/18 edges from 3d62e27 (vertex names):
[1] Daenerys-Targaryen--Drogo Daenerys-Targaryen--Jorah-Mormont
# calculate the degree of each vertex and average degree
degree(graph) Arya-Stark Bran-Stark Bronn Catelyn-Stark
1 3 1 1
Cersei-Lannister Daenerys-Targaryen Eddard-Stark Jeor-Mormont
2 2 5 1
Joffrey-Baratheon Jon-Snow Sansa-Stark Luwin
1 5 2 1
Robb-Stark Tyrion-Lannister Robert-Baratheon Drogo
2 2 2 1
Jorah-Mormont Petyr-Baelish Varys Samwell-Tarly
1 1 1 1
mean(degree(graph)) # average degree[1] 1.8
# calculate geodesic distance between all pairs ofvertices using weights
geo_d <- distances(graph)
head(geo_d) Arya-Stark Bran-Stark Bronn Catelyn-Stark Cersei-Lannister
Arya-Stark 0 Inf Inf Inf Inf
Bran-Stark Inf 0 173 Inf Inf
Bronn Inf 173 0 Inf Inf
Catelyn-Stark Inf Inf Inf 0 133
Cersei-Lannister Inf Inf Inf 133 0
Daenerys-Targaryen Inf Inf Inf Inf Inf
Daenerys-Targaryen Eddard-Stark Jeor-Mormont
Arya-Stark Inf Inf Inf
Bran-Stark Inf Inf 137
Bronn Inf Inf 198
Catelyn-Stark Inf 64 Inf
Cersei-Lannister Inf 69 Inf
Daenerys-Targaryen 0 Inf Inf
Joffrey-Baratheon Jon-Snow Sansa-Stark Luwin Robb-Stark
Arya-Stark 191 Inf 104 Inf Inf
Bran-Stark Inf 56 Inf 65 109
Bronn Inf 117 Inf 238 170
Catelyn-Stark Inf Inf Inf Inf Inf
Cersei-Lannister Inf Inf Inf Inf Inf
Daenerys-Targaryen Inf Inf Inf Inf Inf
Tyrion-Lannister Robert-Baratheon Drogo Jorah-Mormont
Arya-Stark Inf Inf Inf Inf
Bran-Stark 112 Inf Inf Inf
Bronn 61 Inf Inf Inf
Catelyn-Stark Inf 205 Inf Inf
Cersei-Lannister Inf 72 Inf Inf
Daenerys-Targaryen Inf Inf 101 75
Petyr-Baelish Varys Samwell-Tarly
Arya-Stark Inf Inf Inf
Bran-Stark Inf Inf 137
Bronn Inf Inf 198
Catelyn-Stark 145 125 Inf
Cersei-Lannister 150 130 Inf
Daenerys-Targaryen Inf Inf Inf
# calculate unweighted geodesic distance between pairs of vertices, ignoring weights
geo_d <- distances(graph, algorithm = "unweighted")
head(geo_d) Arya-Stark Bran-Stark Bronn Catelyn-Stark Cersei-Lannister
Arya-Stark 0 Inf Inf Inf Inf
Bran-Stark Inf 0 3 Inf Inf
Bronn Inf 3 0 Inf Inf
Catelyn-Stark Inf Inf Inf 0 2
Cersei-Lannister Inf Inf Inf 2 0
Daenerys-Targaryen Inf Inf Inf Inf Inf
Daenerys-Targaryen Eddard-Stark Jeor-Mormont
Arya-Stark Inf Inf Inf
Bran-Stark Inf Inf 2
Bronn Inf Inf 3
Catelyn-Stark Inf 1 Inf
Cersei-Lannister Inf 1 Inf
Daenerys-Targaryen 0 Inf Inf
Joffrey-Baratheon Jon-Snow Sansa-Stark Luwin Robb-Stark
Arya-Stark 2 Inf 1 Inf Inf
Bran-Stark Inf 1 Inf 1 1
Bronn Inf 2 Inf 4 3
Catelyn-Stark Inf Inf Inf Inf Inf
Cersei-Lannister Inf Inf Inf Inf Inf
Daenerys-Targaryen Inf Inf Inf Inf Inf
Tyrion-Lannister Robert-Baratheon Drogo Jorah-Mormont
Arya-Stark Inf Inf Inf Inf
Bran-Stark 2 Inf Inf Inf
Bronn 1 Inf Inf Inf
Catelyn-Stark Inf 2 Inf Inf
Cersei-Lannister Inf 1 Inf Inf
Daenerys-Targaryen Inf Inf 1 1
Petyr-Baelish Varys Samwell-Tarly
Arya-Stark Inf Inf Inf
Bran-Stark Inf Inf 2
Bronn Inf Inf 3
Catelyn-Stark 2 2 Inf
Cersei-Lannister 2 2 Inf
Daenerys-Targaryen Inf Inf Inf
# average distance between pairs of vertices
mean_distance( # uses weights
graph,
unconnected = TRUE,
details = FALSE
)[1] 130.7551
Calculate and plot the degree distribution…
d <- degree_distribution(graph)
# the following two lines will plot the degree distribution
x <- seq(0, length(d)-1, by = 1)
barplot(d, names = x, xlab = "degree", ylab = "rel freq")Write out an {igraph} data to a file…
# in .csv format
write_csv(as_long_data_frame(graph), file = "got.csv")
# in .graphml format
write_graph(graph, file = "got.graphml", format = c("graphml"))Other cool things to explore
Plot an {igraph} object with {visNetwork} directly
visIgraph(graph)# here, graph is an {igraph} object, and visIgraph() is a function from {visNetwork}Convert an {igraph} object to a {visNetwork} object
graph <- toVisNetworkData(graph, idToLabel = TRUE){visNetwork} options
Below are some examples of interesting {visNetwork} options for tweaking graph layouts (e.g., by editing the physics of layout positioning) and for interacting with a graph…
visNetwork(graph$nodes, graph$edges) %>%
visPhysics(
solver = "forceAtlas2Based",
forceAtlas2Based = list(gravitationalConstant = -30)
)visNetwork(graph$nodes, graph$edges) %>%
visPhysics(solver = "repulsion")visNetwork(graph$nodes, graph$edges) %>%
visOptions(
highlightNearest = list(
enabled = TRUE,
hover = TRUE,
degree = 1,
hideColor = "rgba(0,0,0,0.05)")
) %>%
visEdges(color = list(inherit = "to"))visNetwork(graph$nodes, graph$edges) %>%
visOptions(manipulation = TRUE) %>%
visLayout(randomSeed = 42)Visualizing and working with other datasets from the package {igraphdata}
library(igraphdata)
data("foodwebs") # list containing multiple {igraph} objects
graph <- foodwebs$Narragan
visIgraph(graph)data("USairports") # a single {igraph} object
graph <- USairports
visIgraph(USairports)Questions
- Can you calculate the mean degree and plot the degree distribution for each of the new networks graphed above?
Calculating the “importance” of nodes using centrality measures
Let’s go back to the Game of Thrones graph in {igraph} format…
graph <- graph_from_data_frame(
d = edges,
vertices = vertices,
directed = FALSE)
# we can plot this directly with {visNetwork} and can even call igraph layout routines...
visIgraph(graph) %>%
visIgraphLayout(layout = "layout_with_kk") %>%
visLayout(randomSeed = 42)Now, we calculate several standard node-level metrics of centrality, which are common ways of characterizing the importance of different nodes in the network.
Degree centrality (a.k.a. node degree)
degree <- degree(graph)Node strength
strength <- strength(graph)Betweenness
bw_weighted <- betweenness( # with edges weighted...
graph,
directed = FALSE
)
E(graph)$weight <- 1
bw_unweighted <- betweenness( # with edges set to a uniform weight
graph,
directed = FALSE
)Eigen centrality
eigen <- eigen_centrality(graph)$vectorCloseness centrality
closeness <- closeness(graph)Reach
“Reach” is calculated as the number of vertices that can be accessed from any particular vertex (“ego”) within a specified numbers of steps from each that vertex, scaled by the total number of possible vertices that could be accessed
The {igraph} functions, ego_size() or neighborhood(), with two arguments (“graph = graph” and “order = k”), return the number of vertices, including “ego”, within a given number of steps, k, from “ego”, thus ego_size(graph, 1) - 1 is equivalent to degree!
We divide this number by the total number of vertices other than “ego” in the graph to calculate reach
# below, we scale reach by the number of vertices other than ego in the graph, which is a measure of how many possible links ego could have
reach_1 <- (ego_size(graph, 1) - 1) / (vcount(graph) - 1)
reach_2 <- (ego_size(graph, 2) - 1) / (vcount(graph) - 1)
reach_3 <- (ego_size(graph, 3) - 1) / (vcount(graph) - 1)Customizing visualizations
We can put all these measures together in a data frame and add a “shape” variable for visualization in {visNetwork}
centralities <- data.frame(
id = names(degree),
label = names(degree),
degree,
strength,
bw_weighted,
bw_unweighted,
eigen,
hub,
authority,
closeness,
reach_1,
reach_2,
reach_3,
shape = "dot") # add a shape attribute so that we can scale shapes
# using {igraph}
l <- layout_with_kk(graph)
plot.igraph(graph,
layout=l,
vertex.size = degree * 3,
main="Degree")plot.igraph(graph,
layout=l,
vertex.size= eigen * 15,
main="Eigen")plot.igraph(graph,
layout=l,
vertex.size = closeness * 50,
main="Closeness")plot.igraph(graph,
layout=l,
vertex.size = bw_weighted * 2,
main="Betweeness")# using {visNetwork}
graph <- graph_from_data_frame(
d = edges,
vertices = centralities,
directed = FALSE)
size_var <- "bw_weighted" # try changing which variable to plot by replacing "bw_weighted" with other measures of centrality
centralities <- mutate(centralities, value = centralities[[size_var]])
visNetwork(nodes = centralities, edges = edges) %>%
visIgraphLayout(layout = "layout_with_kk") %>%
visLayout(randomSeed = 42)Transitivity or clustering coefficient
The local transitivity or clustering coefficient of a vertex is a measure of how connected the neighbors of that vertex are to one another. It is calculated as…
\[C_{i}=\frac{2\times e_{i}}{k_{i}(k_{i}-1)}\]
… where \(k_{i}\) = the number of neighbors of vertex \(i\) (i.e., the degree of node \(i\)), and \(e_{i}\) = the edges linking neighbors of vertex \(i\).
Global transitivity is the ratio of the overall count of triangles to connected trios of vertices (both connected and unconnected) in the graph.
The {igraph} function transitivity() can be used to calculate these two clustering coefficients
transitivity(graph, type = "local") Arya-Stark Bran-Stark Bronn Catelyn-Stark
NaN 0.3333333 NaN NaN
Cersei-Lannister Daenerys-Targaryen Eddard-Stark Jeor-Mormont
1.0000000 0.0000000 0.1000000 NaN
Joffrey-Baratheon Jon-Snow Sansa-Stark Luwin
NaN 0.1000000 0.0000000 NaN
Robb-Stark Tyrion-Lannister Robert-Baratheon Drogo
1.0000000 0.0000000 1.0000000 NaN
Jorah-Mormont Petyr-Baelish Varys Samwell-Tarly
NaN NaN NaN NaN
transitivity(graph, type = "global")[1] 0.2068966
We can also use this function to calculate the average transitivity across vertices…
transitivity(graph, type = "localaverage")[1] 0.3925926
Transitivity can also be calculated by first converting an {igraph} object to a {network} object using the package {intergraph}, and then using functions bundled in the package {statnet}. To do this conversion, we first install and load the {intergraph} package and then call the conversion function asNetwork()…
library(intergraph)
network <- asNetwork(graph)We then need to install and load {statnet}, which imports functions from two additional useful packages for network analysis, {sna} and {network}…
library(statnet)
# note that we can plot network objects from within the {statnet} package!
# the plotting code below first introduces a new function, `rescale()`, that allows the user to specify a range of values over which to rescale a variable of interest
# then, the `plot.network()` function calls this new function to set vertex sizes...
rescale <- function(variable, low, high) {
min_d <- min(variable)
max_d <- max(variable)
rescaled <- ((high-low) * (variable - min_d)) / (max_d-min_d) + low
return(rescaled)
}
plot.network(network,
vertex.cex = rescale(bw_weighted, 1, 6))The {sna} package loaded with {statnet} includes a function called gtrans(), which calculates global transitivity…
transitivity <- gtrans(network)
transitivity[1] 0.2068966